package org.choerodon.address.service.impl;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.choerodon.address.domain.AreaCrawler;
import org.choerodon.address.domain.AreaVo;
import org.choerodon.address.infra.mapper.AddressMapper;
import org.choerodon.address.service.AddressService;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.*;


/**
 * @author jiu.yang@hand-china.com
 * @version 1.0
 * @description: TODO
 * @date 2021/11/24 8:36
 */
@Service
public class AddressServiceImpl implements AddressService {

    private static Gson gson = new GsonBuilder().create();

    private static String URL_PREFIX = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/";

    private static int count = 0;

    private static Integer c = 0;

    private static final ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
            .setNameFormat("areaCrawler-pool-%d").build();

    @Autowired
    private AddressMapper addressMapper;

    /**
     * 创建线程池
     */
    private static final ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(16), namedThreadFactory,
            new ThreadPoolExecutor.AbortPolicy());
    private static List<AreaVo> areaVoList = new ArrayList();

    @Override
    public String getAddressInfo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            AreaCrawler t = new AreaCrawler();
            Document doc = null;
            do {
                try {
                    doc = t.getDocument("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/index.html");
                    count = 0;
                } catch (Exception e) {
                    count++;
                }
            } while (doc == null);
            //获取所有的省
            List<Future> futureList = new ArrayList<>();
            Elements provinceElements = doc.select("table.provincetable tbody tr.provincetr td");
            for (Element element : provinceElements) {
                if (StringUtils.isNotBlank(element.text())) {
                    AreaVo areaVo = new AreaVo();
                    areaVo.setAddressName(element.text());
                    String cityUrl = URL_PREFIX + element.select("td a").get(0).attr("href");
                    String code = element.select("td a").get(0).attr("href").substring(0, 2) + "0000";
                    areaVo.setAddressCode(code);
                    areaVo.setParentCode("100000");
                    areaVo.setAddressLevel(2);
                    Future future = pool.submit(new CityThread(cityUrl, areaVo));
                    futureList.add(future);
                }
            }
            /**
             * 等待所有的子线程执行完毕
             */
            for (Future future : futureList) {
                future.get();
            }
            //关闭线程池
            pool.shutdownNow();
            AreaVo areaVo = new AreaVo();
            areaVo.setAddressName("中国");
            areaVo.setAddressCode("100000");
            areaVo.setChildren(areaVoList);
            areaVo.setAddressLevel(1);
            Collections.sort(areaVoList, Comparator.comparing(AreaVo::getAddressCode));
            return gson.toJson(areaVo);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }


    private static class CityThread implements Runnable {
        String url;
        AreaVo areaVo;

        public CityThread(String url, AreaVo areaVo) {
            this.url = url;
            this.areaVo = areaVo;
        }

        @Override
        public void run() {
            AreaCrawler t = new AreaCrawler();
            Document cityDocument = null;
            do {
                try {
                    cityDocument = t.getDocument(url);
                    count = 0;
                } catch (Exception e) {
                    //e.printStackTrace();
                    count++;
                }
            } while (cityDocument == null);
            Elements cityElements = cityDocument.select("table.citytable tbody tr.citytr");
            List<AreaVo> cityList = new ArrayList<>();
            for (Element cityElement : cityElements) {
                AreaVo cityVo = new AreaVo();
                String cityCode = cityElement.select("td").get(0).text().substring(0, 6);
                String cityName = cityElement.select("td").get(1).text();
                cityVo.setAddressCode(cityCode);
                cityVo.setAddressName(cityName);
                cityVo.setParentCode(areaVo.getAddressCode());
                cityVo.setAddressLevel(3);
                if (cityElement.select("td a").size() > 0) {
                    String countyUrl = URL_PREFIX + cityElement.select("td a").get(0).attr("href");
                    Document countyDocument = null;
                    do {
                        try {
                            countyDocument = t.getDocument(countyUrl);
                            count = 0;
                        } catch (Exception e) {
                            //e.printStackTrace();
                            count++;
                        }
                    } while (countyDocument == null);
                    Elements countyElements = countyDocument.select("table tbody tr.countytr");
                    List<AreaVo> countyList = new ArrayList<>();
                    for (Element countyElement : countyElements) {
                        AreaVo countyVo = new AreaVo();
                        String countyCode = countyElement.select("td").get(0).text().substring(0, 6);
                        String countyName = countyElement.select("td").get(1).text();
                        countyVo.setAddressCode(countyCode);
                        countyVo.setAddressName(countyName);
                        countyVo.setParentCode(cityVo.getAddressCode());
                        countyVo.setAddressLevel(4);
                        countyList.add(countyVo);
                    }
                    cityVo.setChildren(countyList);
                }
                cityList.add(cityVo);
            }
            areaVo.setChildren(cityList);
            areaVoList.add(areaVo);

        }
    }

    public Document getDocument(String url) throws IOException {
        try {
            Connection conn = Jsoup.connect(url).timeout(5000);
            conn.header("Accept", "*/*");
            conn.header("Accept-Encoding", "gzip, deflate, br");
            conn.header("Accept-Language", "zh-CN,zh;q=0.9");
            conn.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36");
            Connection.Response response = null;
            do {
                response = conn.execute();
                if (count > 0) {
                    Document doc = Jsoup.connect(url)
                            .data("query", "Java")
                            .userAgent("Mozilla")
                            .cookie("auth", "token")
                            .timeout(3000)
                            .post();
                    return doc;
                }
            } while (response.statusCode() != 200);
            return conn.get();
        } catch (IOException e) {
            throw e;
        }
    }


    @Override
    public AreaVo initialization(AreaVo areaVo) {
        String parentCode = areaVo.getParentCode();
        //判断为根节点
        if (parentCode == null || parentCode.length() == 0) {
            this.count(areaVo);
            areaVo.setLeftNode(1);
            areaVo.setRightNode(1 + (c - 1) * 2 + 1);
            c = 0;
        }
        //下层节点
        this.initial(areaVo);
        return areaVo;
    }

    @Override
    public List<AreaVo> save(AreaVo areaVo) {
        this.collect(areaVo);
        for (AreaVo area : collect) {
            addressMapper.insertSelective(area);
        }
        return collect;
    }


    @Override
    public List<AreaVo> queryById(Long id) {
        List<AreaVo> areaVos = addressMapper.queryById(id);
        return areaVos;
    }

    @Override
    public Boolean add(AreaVo areaVo) {

        /**
         * 区分是子分类新增还是同级分类新增
         *
         * 子分类新增 - 该节点没有兄弟节点 即该父节点没有儿子节点 该节点划分(父节点的左节点+1,父节点的左节点+2)
         * 所有右节点大于插入点(父节点)左节点值的节点左右节点都加2
         *
         * 同级分类新增 - 该节点存在兄弟节点 该节点划分(最大的兄弟节点右节点+1,最大的兄弟节点右节点+2)
         * 所有右节点大于插入点(最大兄弟节点)右节点值的节点左右节点都加2
         *
         **/
        String parentCode = areaVo.getParentCode();
        AreaVo area = new AreaVo();
        area.setParentCode(parentCode);
        List<AreaVo> ars = addressMapper.select(area);
        if (ars != null && ars.size() != 0) {
            //同级分类新增
            //寻找其最大兄弟节点
            AreaVo maxBro = ars.stream().max(Comparator.comparing(AreaVo::getRightNode)).get();
            Integer rightNode = maxBro.getRightNode();
            AreaVo params = new AreaVo();
            params.setAddressLevel(maxBro.getAddressLevel());
            params.setParentCode(maxBro.getParentCode());
            params.setLeftNode(rightNode + 1);
            params.setRightNode(rightNode + 2);
            params.setAddressCode(areaVo.getAddressCode());
            params.setAddressName(areaVo.getAddressName());
            addressMapper.insertSelective(params);
            //待处理数据
            List<AreaVo> updateArea = addressMapper.queryRightNode(rightNode);
            for (AreaVo update : updateArea) {
                update.setRightNode(update.getRightNode() + 2);
                update.setLeftNode(update.getLeftNode() + 2);
                addressMapper.updateByPrimaryKeySelective(update);
            }
        } else {
            //子分类新增
            AreaVo para = new AreaVo();
            para.setAddressCode(parentCode);
            List<AreaVo> areaVos = addressMapper.select(para);
            if (areaVos == null || areaVos.size() == 0) {
                throw new RuntimeException("无效地址编码");
            }
            Integer leftNode = areaVos.get(0).getLeftNode();
            AreaVo params = new AreaVo();
            params.setAddressLevel(areaVos.get(0).getAddressLevel() + 1);
            params.setParentCode(parentCode);
            params.setLeftNode(leftNode + 1);
            params.setRightNode(leftNode + 2);
            params.setAddressCode(areaVo.getAddressCode());
            params.setAddressName(areaVo.getAddressName());
            addressMapper.insertSelective(params);
            List<AreaVo> updateArea = addressMapper.queryLeftNode(leftNode);
            for (AreaVo update : updateArea) {
                update.setRightNode(update.getRightNode() + 2);
                update.setLeftNode(update.getLeftNode() + 2);
                addressMapper.updateByPrimaryKeySelective(update);
            }
        }
        return true;
    }


    private List<AreaVo> collect = new ArrayList<>();

    private void collect(AreaVo areaVo) {
        List<AreaVo> childrens = areaVo.getChildren();
        collect.add(areaVo);
        if (childrens != null && childrens.size() != 0) {
            for (AreaVo children : childrens) {
                collect(children);
            }
        }
    }

    private void initial(AreaVo areaVo) {
        List<AreaVo> childrens = areaVo.getChildren();
        Integer left = areaVo.getLeftNode();
        if (childrens != null && childrens.size() != 0) {
            this.initialization(childrens, left);
            for (AreaVo area : childrens) {
                this.initial(area);
            }
        }
    }

    /**
     * 子节点的左右节点赋值
     *
     * @param
     * @return
     * @throws RuntimeException exception description
     * @version 1.0
     */
    private void initialization(List<AreaVo> childrens, Integer parentLeftCode) {
        for (int i = 0; i < childrens.size(); i++) {
            //父节点左节点已知 初始赋值左节点为父节点左节点+1
            if (i == 0) {
                AreaVo areaVo = childrens.get(i);
                areaVo.setLeftNode(parentLeftCode + 1);
                this.count(areaVo);
                areaVo.setRightNode(areaVo.getLeftNode() + (c - 1) * 2 + 1);
                c = 0;
            } else {
                //兄弟节点的右节点+1 = 左节点
                AreaVo areaBro = childrens.get(i - 1);
                Integer right = areaBro.getRightNode();
                AreaVo area = childrens.get(i);
                area.setLeftNode(right + 1);
                this.count(area);
                area.setRightNode(area.getLeftNode() + (c - 1) * 2 + 1);
                c = 0;
            }
        }
    }

    /**
     * 统计节点数量
     *
     * @param
     * @return
     * @throws RuntimeException exception description
     * @version 1.0
     */
    private void count(AreaVo areaVo) {
        List<AreaVo> areaVos = areaVo.getChildren();
        if (areaVos == null || areaVos.size() == 0) {
            c++;
        } else {
            c++;
            for (AreaVo area : areaVos) {
                this.count(area);
            }
        }

    }


    @Override
    public Boolean delete(AreaVo areaVo) {
        /**
         * 1.得到删除节点的左节点值 右节点值 以及差值@myDiff:=@myRight-@myLeft+1
         * 2. 删除节点以及其子节点
         * 3. 更新受到影响节点的左右节点值
         */
        AreaVo params = addressMapper.queryDeleteNode(areaVo.getAddressCode());
        Integer leftNode = params.getLeftNode();
        Integer rightNode = params.getRightNode();
        Integer diff = rightNode - leftNode + 1;
        addressMapper.deleteNode(rightNode,leftNode);
        addressMapper.updateRight(rightNode,leftNode,diff);
        addressMapper.updateLeft(rightNode,leftNode,diff);
        return null;
    }
}
